<?php
/**
 * HttpRequest
 * @author Tongle Xu <xutongle@gmail.com> 2013-9-14
 * @copyright Copyright (c) 2003-2103 tintsoft.com
 * @license http://www.tintsoft.com
 * @version $Id$
 */
class HttpRequest extends Object {
	private $_requestUri;
	private $_pathInfo;
	private $_scriptFile;
	private $_scriptUrl;
	private $_hostInfo;
	private $_baseUrl;
	private $_cookies;
	private $_preferredAcceptTypes;
	private $_preferredLanguages;
	private $_restParams;
	private $_port;
	private $_securePort;
	
	/**
	 * 初始化
	 */
	public function init() {
		$this->normalizeRequest ();
	}
	
	/**
	 * 请求数据规范化
	 */
	protected function normalizeRequest() {
		if (function_exists ( 'get_magic_quotes_gpc' ) && get_magic_quotes_gpc ()) {
			if (isset ( $_GET )) $_GET = $this->stripSlashes ( $_GET );
			if (isset ( $_POST )) $_POST = $this->stripSlashes ( $_POST );
			if (isset ( $_REQUEST )) $_REQUEST = $this->stripSlashes ( $_REQUEST );
			if (isset ( $_COOKIE )) $_COOKIE = $this->stripSlashes ( $_COOKIE );
		}
	}
	
	/**
	 * 删除由 addslashes() 函数添加的反斜杠
	 *
	 * @param unknown $data
	 * @return unknown multitype: string
	 */
	public function stripSlashes(&$data) {
		if (is_array ( $data )) {
			if (count ( $data ) == 0) return $data;
			$keys = array_map ( 'stripslashes', array_keys ( $data ) );
			$data = array_combine ( $keys, array_values ( $data ) );
			return array_map ( array ($this,'stripSlashes' ), $data );
		} else
			return stripslashes ( $data );
	}
	
	/**
	 * 从GET或POST获取参数
	 *
	 * @param string $name 参数名称
	 * @param string $defaultValue 参数默认值
	 * @return string 参数值
	 */
	public function getParam($name, $defaultValue = null) {
		return isset ( $_GET [$name] ) ? $_GET [$name] : (isset ( $_POST [$name] ) ? $_POST [$name] : $defaultValue);
	}
	
	/**
	 * 从GET获取参数
	 *
	 * @param string $name 参数名称
	 * @param string $defaultValue 参数默认值
	 * @return string 参数值
	 */
	public function getQuery($name, $defaultValue = null) {
		return isset ( $_GET [$name] ) ? $_GET [$name] : $defaultValue;
	}
	
	/**
	 * 从POST获取参数
	 *
	 * @param string $name 参数名称
	 * @param string $defaultValue 参数默认值
	 * @return string 参数值
	 */
	public function getPost($name, $defaultValue = null) {
		return isset ( $_POST [$name] ) ? $_POST [$name] : $defaultValue;
	}
	
	/**
	 * 从Delete获取参数
	 *
	 * @param string $name 参数名称
	 * @param string $defaultValue 参数默认值
	 * @return string 参数值
	 */
	public function getDelete($name, $defaultValue = null) {
		if ($this->getIsDeleteViaPostRequest ()) return $this->getPost ( $name, $defaultValue );
		if ($this->getIsDeleteRequest ()) {
			$restParams = $this->getRestParams ();
			return isset ( $restParams [$name] ) ? $restParams [$name] : $defaultValue;
		} else
			return $defaultValue;
	}
	
	/**
	 * 从Put获取参数
	 *
	 * @param string $name 参数名称
	 * @param string $defaultValue 参数默认值
	 * @return string 参数值
	 */
	public function getPut($name, $defaultValue = null) {
		if ($this->getIsPutViaPostRequest ()) return $this->getPost ( $name, $defaultValue );
		if ($this->getIsPutRequest ()) {
			$restParams = $this->getRestParams ();
			return isset ( $restParams [$name] ) ? $restParams [$name] : $defaultValue;
		} else
			return $defaultValue;
	}
	
	/**
	 * 返回cookie的值
	 *
	 * 如果$name=null则返回所有Cookie值
	 *
	 * @param string $name 获取的变量名,如果该值为null则返回$_COOKIE数组,默认为null
	 * @param string $defaultValue 当获取变量失败的时候返回该值,默认该值为null
	 * @return mixed
	 */
	public static function getCookie($name = null, $defaultValue = null) {
		if ($name === null) return $_COOKIE;
		return (isset ( $_COOKIE [$name] )) ? $_COOKIE [$name] : $defaultValue;
	}
	
	/**
	 * 返回请求参数
	 *
	 * @return multitype:
	 */
	public function getRestParams() {
		if ($this->_restParams === null) {
			$result = array ();
			if (function_exists ( 'mb_parse_str' ))
				mb_parse_str ( $this->getRawBody (), $result );
			else
				parse_str ( $this->getRawBody (), $result );
			$this->_restParams = $result;
		}
		return $this->_restParams;
	}
	
	/**
	 * 返回原始HTTP请求体
	 *
	 * @return string 原始HTTP请求体
	 */
	public function getRawBody() {
		static $rawBody;
		if ($rawBody === null) $rawBody = file_get_contents ( 'php://input' );
		return $rawBody;
	}
	
	/**
	 * 返回当前请求的URL
	 *
	 * @return string
	 */
	public function getUrl() {
		return $this->getRequestUri ();
	}
	
	/**
	 * 获得主机信息，包含协议信息，主机名，访问端口信息
	 * <pre>Example:
	 * 请求: http://www.tintsoft.com/example/index.php?a=test
	 * 返回： http://www.tintsoft.com
	 * </pre>
	 *
	 * @param string $schema 模式(http、https)如果空,使用的模式将使用当前的请求
	 * @return string 主机信息
	 */
	public function getHostInfo($schema = '') {
		if ($this->_hostInfo === null) {
			if ($secure = $this->getIsSecureConnection ())
				$http = 'https';
			else
				$http = 'http';
			if (isset ( $_SERVER ['HTTP_HOST'] ))
				$this->_hostInfo = $http . '://' . $_SERVER ['HTTP_HOST'];
			else {
				$this->_hostInfo = $http . '://' . $_SERVER ['SERVER_NAME'];
				$port = $secure ? $this->getSecurePort () : $this->getPort ();
				if (($port !== 80 && ! $secure) || ($port !== 443 && $secure)) $this->_hostInfo .= ':' . $port;
			}
		}
		if ($schema !== '') {
			$secure = $this->getIsSecureConnection ();
			if ($secure && $schema === 'https' || ! $secure && $schema === 'http') return $this->_hostInfo;
			
			$port = $schema === 'https' ? $this->getSecurePort () : $this->getPort ();
			if ($port !== 80 && $schema === 'http' || $port !== 443 && $schema === 'https')
				$port = ':' . $port;
			else
				$port = '';
			
			$pos = strpos ( $this->_hostInfo, ':' );
			return $schema . substr ( $this->_hostInfo, $pos, strcspn ( $this->_hostInfo, ':', $pos + 1 ) + 1 ) . $port;
		} else
			return $this->_hostInfo;
	}
	
	/**
	 * 设置主机信息
	 *
	 * @param unknown $value
	 */
	public function setHostInfo($value) {
		$this->_hostInfo = rtrim ( $value, '/' );
	}
	
	/**
	 * 获取基础URL
	 * 这里是去除了脚本文件以及访问参数信息的URL地址信息:
	 * <pre>Example:
	 * 请求: http://www.tintsoft.com/example/index.php?a=test
	 * 1]如果: $absolute = false：
	 * 返回： example
	 * 2]如果: $absolute = true:
	 * 返回： http://www.tintsoft.com/example
	 * </pre>
	 *
	 * @param string $absolute
	 * @return string 基础URL
	 */
	public function getBaseUrl($absolute = false) {
		if ($this->_baseUrl === null) $this->_baseUrl = rtrim ( dirname ( $this->getScriptUrl () ), '\\/' );
		return $absolute ? $this->getHostInfo () . $this->_baseUrl : $this->_baseUrl;
	}
	
	/**
	 * 设置基础URL
	 *
	 * @param string $value 基础URL
	 */
	public function setBaseUrl($value) {
		$this->_baseUrl = $value;
	}
	
	/**
	 * 返回当前执行脚本的绝对路径
	 * <pre>Example:
	 * 请求: http://www.tintsoft.com/example/index.php?a=test
	 * 返回: /example/index.php
	 * </pre>
	 *
	 * @throws Exception
	 * @return string 当前执行脚本的绝对路径
	 */
	public function getScriptUrl() {
		if ($this->_scriptUrl === null) {
			$scriptName = basename ( $_SERVER ['SCRIPT_FILENAME'] );
			if (basename ( $_SERVER ['SCRIPT_NAME'] ) === $scriptName)
				$this->_scriptUrl = $_SERVER ['SCRIPT_NAME'];
			elseif (basename ( $_SERVER ['PHP_SELF'] ) === $scriptName)
				$this->_scriptUrl = $_SERVER ['PHP_SELF'];
			elseif (isset ( $_SERVER ['ORIG_SCRIPT_NAME'] ) && basename ( $_SERVER ['ORIG_SCRIPT_NAME'] ) === $scriptName)
				$this->_scriptUrl = $_SERVER ['ORIG_SCRIPT_NAME'];
			elseif (($pos = strpos ( $_SERVER ['PHP_SELF'], '/' . $scriptName )) !== false)
				$this->_scriptUrl = substr ( $_SERVER ['SCRIPT_NAME'], 0, $pos ) . '/' . $scriptName;
			elseif (isset ( $_SERVER ['DOCUMENT_ROOT'] ) && strpos ( $_SERVER ['SCRIPT_FILENAME'], $_SERVER ['DOCUMENT_ROOT'] ) === 0)
				$this->_scriptUrl = str_replace ( '\\', '/', str_replace ( $_SERVER ['DOCUMENT_ROOT'], '', $_SERVER ['SCRIPT_FILENAME'] ) );
			else
				throw new Exception ( 'HttpRequest is unable to determine the entry script URL.' );
		}
		return $this->_scriptUrl;
	}
	
	/**
	 * 设置当前执行脚本的绝对路径
	 *
	 * @param string $value 当前执行脚本的绝对路径
	 */
	public function setScriptUrl($value) {
		$this->_scriptUrl = '/' . trim ( $value, '/' );
	}
	
	/**
	 * 返回当前请求的URL路径信息。
	 * 返回包含由客户端提供的、跟在真实脚本名称之后并且在查询语句（query string）之前的路径信息
	 * <pre>Example:
	 * 请求: http://www.tintsoft.com/example/index.php/site/main?a=test
	 * 返回: site/main
	 * </pre>
	 *
	 * @throws Exception
	 * @return string 当前请求的URL的路径信息
	 */
	public function getPathInfo() {
		if ($this->_pathInfo === null) {
			$pathInfo = $this->getRequestUri ();
			if (($pos = strpos ( $pathInfo, '?' )) !== false) $pathInfo = substr ( $pathInfo, 0, $pos );
			$pathInfo = $this->decodePathInfo ( $pathInfo );
			$scriptUrl = $this->getScriptUrl ();
			$baseUrl = $this->getBaseUrl ();
			if (strpos ( $pathInfo, $scriptUrl ) === 0)
				$pathInfo = substr ( $pathInfo, strlen ( $scriptUrl ) );
			elseif ($baseUrl === '' || strpos ( $pathInfo, $baseUrl ) === 0)
				$pathInfo = substr ( $pathInfo, strlen ( $baseUrl ) );
			elseif (strpos ( $_SERVER ['PHP_SELF'], $scriptUrl ) === 0)
				$pathInfo = substr ( $_SERVER ['PHP_SELF'], strlen ( $scriptUrl ) );
			else
				throw new Exception ( 'HttpRequest is unable to determine the path info of the request.' );
			$this->_pathInfo = trim ( $pathInfo, '/' );
		}
		return $this->_pathInfo;
	}
	
	/**
	 * 解码路径信息
	 *
	 * @param string $pathInfo
	 * @return string 路径信息
	 */
	protected function decodePathInfo($pathInfo) {
		$pathInfo = urldecode ( $pathInfo );
		if (preg_match ( '%^(?:
		   [\x09\x0A\x0D\x20-\x7E]            # ASCII
		 | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
		 | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
		 | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
		 | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
		 | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
		 | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
		 | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
		)*$%xs', $pathInfo )) {
			return $pathInfo;
		} else {
			return utf8_encode ( $pathInfo );
		}
	}
	
	/**
	 * 返回请求的资源标识符
	 * <pre>这里的uri是去除协议名、主机名的
	 * Example:
	 * 请求： http://www.tintsoft.com/example/index.php?a=test
	 * 则返回: /example/index.php?a=test
	 * </pre>
	 *
	 * @throws Exception
	 * @return string 返回请求的资源标识符
	 */
	public function getRequestUri() {
		if ($this->_requestUri === null) {
			if (isset ( $_SERVER ['HTTP_X_REWRITE_URL'] )) // IIS
				$this->_requestUri = $_SERVER ['HTTP_X_REWRITE_URL'];
			elseif (isset ( $_SERVER ['REQUEST_URI'] )) {
				$this->_requestUri = $_SERVER ['REQUEST_URI'];
				if (! empty ( $_SERVER ['HTTP_HOST'] )) {
					if (strpos ( $this->_requestUri, $_SERVER ['HTTP_HOST'] ) !== false) $this->_requestUri = preg_replace ( '/^\w+:\/\/[^\/]+/', '', $this->_requestUri );
				} else
					$this->_requestUri = preg_replace ( '/^(http|https):\/\/[^\/]+/i', '', $this->_requestUri );
			} elseif (isset ( $_SERVER ['ORIG_PATH_INFO'] )) 			// IIS 5.0 CGI
			{
				$this->_requestUri = $_SERVER ['ORIG_PATH_INFO'];
				if (! empty ( $_SERVER ['QUERY_STRING'] )) $this->_requestUri .= '?' . $_SERVER ['QUERY_STRING'];
			} else
				throw new Exception ( 'HttpRequest is unable to determine the request URI.' );
		}
		
		return $this->_requestUri;
	}
	
	/**
	 * 返回查询字符串
	 *
	 * @return string 查询字符串
	 */
	public function getQueryString() {
		return isset ( $_SERVER ['QUERY_STRING'] ) ? $_SERVER ['QUERY_STRING'] : '';
	}
	
	/**
	 * 请求是否使用的是HTTPS安全链接
	 * 如果是安全请求则返回true否则返回false
	 *
	 * @return boolean 是否使用的是HTTPS安全链接
	 */
	public function getIsSecureConnection() {
		return isset ( $_SERVER ['HTTPS'] ) && ($_SERVER ['HTTPS'] == 'on' || $_SERVER ['HTTPS'] == 1) || isset ( $_SERVER ['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER ['HTTP_X_FORWARDED_PROTO'] == 'https';
	}
	
	/**
	 * 获得请求类型
	 *
	 * @return string 请求类型
	 */
	public function getRequestType() {
		if (isset ( $_POST ['_method'] )) return strtoupper ( $_POST ['_method'] );
		return strtoupper ( isset ( $_SERVER ['REQUEST_METHOD'] ) ? $_SERVER ['REQUEST_METHOD'] : 'GET' );
	}
	
	/**
	 * 返回是否是POST请求
	 *
	 * @return boolean 是否是POST请求
	 */
	public function getIsPostRequest() {
		return isset ( $_SERVER ['REQUEST_METHOD'] ) && ! strcasecmp ( $_SERVER ['REQUEST_METHOD'], 'POST' );
	}
	
	/**
	 * 返回是否是Delete请求
	 *
	 * @return boolean 是否是Delete请求
	 */
	public function getIsDeleteRequest() {
		return (isset ( $_SERVER ['REQUEST_METHOD'] ) && ! strcasecmp ( $_SERVER ['REQUEST_METHOD'], 'DELETE' )) || $this->getIsDeleteViaPostRequest ();
	}
	
	/**
	 * 返回是否是Delete请求,通过POST
	 *
	 * @return boolean 是否是Delete请求
	 */
	protected function getIsDeleteViaPostRequest() {
		return isset ( $_POST ['_method'] ) && ! strcasecmp ( $_POST ['_method'], 'DELETE' );
	}
	
	/**
	 * 返回是否是PUT请求
	 *
	 * @return boolean 是否是PUT请求
	 */
	public function getIsPutRequest() {
		return (isset ( $_SERVER ['REQUEST_METHOD'] ) && ! strcasecmp ( $_SERVER ['REQUEST_METHOD'], 'PUT' )) || $this->getIsPutViaPostRequest ();
	}
	
	/**
	 * 返回是否是PUT请求，通过POST
	 *
	 * @return boolean 是否是PUT请求
	 */
	protected function getIsPutViaPostRequest() {
		return isset ( $_POST ['_method'] ) && ! strcasecmp ( $_POST ['_method'], 'PUT' );
	}
	
	/**
	 * 返回是否这是一个AJAX(XMLHttpRequest)请求
	 *
	 * @return boolean 是否这是一个AJAX(XMLHttpRequest)请求
	 */
	public function getIsAjaxRequest() {
		return isset ( $_SERVER ['HTTP_X_REQUESTED_WITH'] ) && $_SERVER ['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
	}
	
	/**
	 * 返回是否是Adobe Flash或Adobe Flex的请求
	 *
	 * @return boolean 是否是Adobe Flash或Adobe Flex的请求
	 */
	public function getIsFlashRequest() {
		return isset ( $_SERVER ['HTTP_USER_AGENT'] ) && (stripos ( $_SERVER ['HTTP_USER_AGENT'], 'Shockwave' ) !== false || stripos ( $_SERVER ['HTTP_USER_AGENT'], 'Flash' ) !== false);
	}
	
	/**
	 * 返回当前运行脚本所在的服务器的主机名。
	 * 如果脚本运行于虚拟主机中
	 * 该名称是由那个虚拟主机所设置的值决定
	 *
	 * @return string 主机名
	 */
	public function getServerName() {
		return $_SERVER ['SERVER_NAME'];
	}
	
	/**
	 * 返回服务端口号
	 * https链接的默认端口号为443
	 * http链接的默认端口号为80
	 *
	 * @return string 端口号
	 */
	public function getServerPort() {
		return $_SERVER ['SERVER_PORT'];
	}
	
	/**
	 * 返回浏览器发送Referer请求头
	 * 可以让服务器了解和追踪发出本次请求的起源URL地址
	 *
	 * @return string 来源URL地址
	 */
	public function getUrlReferrer() {
		return isset ( $_SERVER ['HTTP_REFERER'] ) ? $_SERVER ['HTTP_REFERER'] : null;
	}
	
	/**
	 * 返回User-Agent头字段用于指定浏览器或者其他客户端程序的类型和名字
	 * 如果客户机是一种无线手持终端，就返回一个WML文件；如果发现客户端是一种普通浏览器，
	 * 则返回通常的HTML文件
	 *
	 * @return string User-Agent字段内容
	 */
	public function getUserAgent() {
		return isset ( $_SERVER ['HTTP_USER_AGENT'] ) ? $_SERVER ['HTTP_USER_AGENT'] : null;
	}
	
	/**
	 * 返回用户IP地址
	 *
	 * @return string 用户IP地址
	 */
	public function getUserHostAddress() {
		return isset ( $_SERVER ['REMOTE_ADDR'] ) ? $_SERVER ['REMOTE_ADDR'] : '127.0.0.1';
	}
	
	/**
	 * 返回用户主机名称,null如果它不能确定
	 *
	 * @return string 用户主机名称
	 */
	public function getUserHost() {
		return isset ( $_SERVER ['REMOTE_HOST'] ) ? $_SERVER ['REMOTE_HOST'] : null;
	}
	
	/**
	 * 返回当前脚本文件的路径
	 *
	 * @return string 脚本文件路径
	 */
	public function getScriptFile() {
		if ($this->_scriptFile !== null)
			return $this->_scriptFile;
		else
			return $this->_scriptFile = realpath ( $_SERVER ['SCRIPT_FILENAME'] );
	}
	
	/**
	 * 返回用户浏览器的性能
	 *
	 * @param string $userAgent
	 * @return mixed 用户浏览器的性能
	 */
	public function getBrowser($userAgent = null) {
		return get_browser ( $userAgent, true );
	}
	
	/**
	 * 返回用户的浏览器接受类型
	 *
	 * @return string 用户浏览器接受类型
	 */
	public function getAcceptTypes() {
		return isset ( $_SERVER ['HTTP_ACCEPT'] ) ? $_SERVER ['HTTP_ACCEPT'] : null;
	}
	
	/**
	 * 返回端口(非HTTPS)
	 *
	 * @return number 端口
	 */
	public function getPort() {
		if ($this->_port === null) $this->_port = ! $this->getIsSecureConnection () && isset ( $_SERVER ['SERVER_PORT'] ) ? ( int ) $_SERVER ['SERVER_PORT'] : 80;
		return $this->_port;
	}
	
	/**
	 * 设置端口(非HTTPS)
	 *
	 * @param number $value 端口
	 */
	public function setPort($value) {
		$this->_port = ( int ) $value;
		$this->_hostInfo = null;
	}
	
	/**
	 * 返回端口(HTTPS)
	 *
	 * @return number 端口
	 */
	public function getSecurePort() {
		if ($this->_securePort === null) $this->_securePort = $this->getIsSecureConnection () && isset ( $_SERVER ['SERVER_PORT'] ) ? ( int ) $_SERVER ['SERVER_PORT'] : 443;
		return $this->_securePort;
	}
	
	/**
	 * 设置端口(HTTPS)
	 *
	 * @param number $value 端口
	 */
	public function setSecurePort($value) {
		$this->_securePort = ( int ) $value;
		$this->_hostInfo = null;
	}
	
	/**
	 * 解析一个HTTP Accept标头,返回一个数组映射
	 *
	 * Example:
	 * <code>'application/xhtml+xml;q=0.9;level=1'</code>
	 * <pre>
	 * array(
	 * 'type' => 'application',
	 * 'subType' => 'xhtml',
	 * 'baseType' => 'xml',
	 * 'params' => array(
	 * 'q' => 0.9,
	 * 'level' => '1',
	 * ),
	 * )
	 * </pre>
	 *
	 * @param string $header Accept标头
	 * @return array 数组映射
	 */
	public static function parseAcceptHeader($header) {
		$matches = array ();
		$accepts = array ();
		// get individual entries with their type, subtype, basetype and params
		preg_match_all ( '/(?:\G\s?,\s?|^)(\w+|\*)\/(\w+|\*)(?:\+(\w+))?|(?<!^)\G(?:\s?;\s?(\w+)=([\w\.]+))/', $header, $matches );
		// the regexp should (in theory) always return an array of 6 arrays
		if (count ( $matches ) === 6) {
			$i = 0;
			$itemLen = count ( $matches [1] );
			while ( $i < $itemLen ) {
				// fill out a content type
				$accept = array ('type' => $matches [1] [$i],'subType' => $matches [2] [$i],'baseType' => null,'params' => array () );
				// fill in the base type if it exists
				if ($matches [3] [$i] !== null && $matches [3] [$i] !== '') $accept ['baseType'] = $matches [3] [$i];
				// continue looping while there is no new content type, to fill
				// in all accompanying params
				for($i ++; $i < $itemLen; $i ++) {
					// if the next content type is null, then the item is a
					// param for the current content type
					if ($matches [1] [$i] === null || $matches [1] [$i] === '') {
						// if this is the quality param, convert it to a double
						if ($matches [4] [$i] === 'q') {
							// sanity check on q value
							$q = ( double ) $matches [5] [$i];
							if ($q > 1)
								$q = ( double ) 1;
							elseif ($q < 0)
								$q = ( double ) 0;
							$accept ['params'] [$matches [4] [$i]] = $q;
						} else
							$accept ['params'] [$matches [4] [$i]] = $matches [5] [$i];
					} else
						break;
				}
				// q defaults to 1 if not explicitly given
				if (! isset ( $accept ['params'] ['q'] )) $accept ['params'] ['q'] = ( double ) 1;
				$accepts [] = $accept;
			}
		}
		return $accepts;
	}
	public static function compareAcceptTypes($a, $b) {
		// check for equal quality first
		if ($a ['params'] ['q'] === $b ['params'] ['q'])
			if (! ($a ['type'] === '*' xor $b ['type'] === '*'))
				if (! ($a ['subType'] === '*' xor $b ['subType'] === '*'))
					// finally, higher number of parameters counts as greater
					// precedence
					if (count ( $a ['params'] ) === count ( $b ['params'] ))
						return 0;
					else
						return count ( $a ['params'] ) < count ( $b ['params'] ) ? 1 : - 1;
					// more specific takes precedence - whichever one doesn't
					// have a * subType
				else
					return $a ['subType'] === '*' ? 1 : - 1;
				// more specific takes precedence - whichever one doesn't have a
				// * type
			else
				return $a ['type'] === '*' ? 1 : - 1;
		else
			return ($a ['params'] ['q'] < $b ['params'] ['q']) ? 1 : - 1;
	}
	public function getPreferredAcceptTypes() {
		if ($this->_preferredAcceptTypes === null) {
			$accepts = self::parseAcceptHeader ( $this->getAcceptTypes () );
			usort ( $accepts, array (get_class ( $this ),'compareAcceptTypes' ) );
			$this->_preferredAcceptTypes = $accepts;
		}
		return $this->_preferredAcceptTypes;
	}
	
	/**
	 * 返回用户优先接受的MIME类型。
	 *
	 * @return array
	 */
	public function getPreferredAcceptType() {
		$preferredAcceptTypes = $this->getPreferredAcceptTypes ();
		return empty ( $preferredAcceptTypes ) ? false : $preferredAcceptTypes [0];
	}
	
	/**
	 * 返回用户可接受的语言数组
	 *
	 * @return array 语言数组
	 */
	public function getPreferredLanguages() {
		if ($this->_preferredLanguages === null) {
			$sortedLanguages = array ();
			if (isset ( $_SERVER ['HTTP_ACCEPT_LANGUAGE'] ) && $n = preg_match_all ( '/([\w\-_]+)(?:\s*;\s*q\s*=\s*(\d*\.?\d*))?/', $_SERVER ['HTTP_ACCEPT_LANGUAGE'], $matches )) {
				$languages = array ();
				
				for($i = 0; $i < $n; ++ $i) {
					$q = $matches [2] [$i];
					if ($q === '') $q = 1;
					if ($q) $languages [] = array (( float ) $q,$matches [1] [$i] );
				}
				usort ( $languages, create_function ( '$a,$b', 'if($a[0]==$b[0]) {return 0;} return ($a[0]<$b[0]) ? 1 : -1;' ) );
				foreach ( $languages as $language )
					$sortedLanguages [] = $language [1];
			}
			$this->_preferredLanguages = $sortedLanguages;
		}
		return $this->_preferredLanguages;
	}
}